#include "WordEditDialog.h"

#include <Configuration.h>

#include "StringUtil.h"
#include "ValidateExpressionThread.h"
#include "ui_WordEditDialog.h"

WordEditDialog::WordEditDialog(QWidget* parent)
    : QDialog(parent),
      ui(new Ui::WordEditDialog),
      mHexLineEditPos(0),
      mSignedEditPos(0),
      mUnsignedEditPos(0),
      mAsciiLineEditPos(0) {
  ui->setupUi(this);
  setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint |
                 Qt::MSWindowsFixedSizeDialogHint);
  setModal(true);

  // Set up default validators for numerical input
  ui->signedLineEdit->setValidator(new QRegExpValidator(
      QRegExp("^-?\\d*(\\d+)?$"), this));  // Optional negative, 0-9
  ui->unsignedLineEdit->setValidator(
      new QRegExpValidator(QRegExp("^\\d*(\\d+)?$"), this));  // No signs, 0-9

  mValidateThread = new ValidateExpressionThread(this);
  mValidateThread->setOnExpressionChangedCallback(std::bind(
      &WordEditDialog::validateExpression, this, std::placeholders::_1));

  connect(mValidateThread, SIGNAL(expressionChanged(bool, bool, dsint)), this,
          SLOT(expressionChanged(bool, bool, dsint)));
  connect(ui->expressionLineEdit, SIGNAL(textChanged(QString)), mValidateThread,
          SLOT(textChanged(QString)));
  mWord = 0;
  Config()->setupWindowPos(this);
}

void WordEditDialog::validateExpression(QString expression) {
  bool ok = false;
  duint value = expression.toULong(&ok, 16);
  bool validExpression = ok;
  bool validPointer = validExpression;
  this->mValidateThread->emitExpressionChanged(validExpression, validPointer,
                                               value);
}

WordEditDialog::~WordEditDialog() {
  Config()->saveWindowPos(this);
  mValidateThread->stop();
  mValidateThread->wait();
  delete ui;
}

void WordEditDialog::showEvent(QShowEvent* event) {
  Q_UNUSED(event);
  mValidateThread->start();
}

void WordEditDialog::hideEvent(QHideEvent* event) {
  Q_UNUSED(event);
  mValidateThread->stop();
  mValidateThread->wait();
}

void WordEditDialog::setup(QString title, duint defVal, int byteCount) {
  this->setWindowTitle(title);
  ui->hexLineEdit->setInputMask(QString("hh").repeated(byteCount));
  ui->expressionLineEdit->setText(
      QString("%1").arg(defVal, byteCount * 2, 16, QChar('0')).toUpper());

  ui->expressionLineEdit->selectAll();
  ui->expressionLineEdit->setFocus();
}

duint WordEditDialog::getVal() { return mWord; }

void WordEditDialog::expressionChanged(bool validExpression, bool validPointer,
                                       dsint value) {
  Q_UNUSED(validPointer);
  if (validExpression) {
    ui->btnOk->setEnabled(true);

    // hex
    mWord = value;
    duint hexWord = 0;
    unsigned char* hex = (unsigned char*)&hexWord;
    unsigned char* word = (unsigned char*)&mWord;
    // ascii
    int asciiWidth = 0;
#ifdef _WIN64
    hex[0] = word[7];
    hex[1] = word[6];
    hex[2] = word[5];
    hex[3] = word[4];
    hex[4] = word[3];
    hex[5] = word[2];
    hex[6] = word[1];
    hex[7] = word[0];
    asciiWidth = 8;
#else   // x86
    hex[0] = word[3];
    hex[1] = word[2];
    hex[2] = word[1];
    hex[3] = word[0];
    asciiWidth = 4;
#endif  //_WIN64

    // Save the original index for inputs
    saveCursorPositions();

    // Byte edit line
    ui->hexLineEdit->setText(ToPtrString(hexWord));
    // Signed edit
    ui->signedLineEdit->setText(QString::number((dsint)mWord));
    // Unsigned edit
    ui->unsignedLineEdit->setText(QString::number((duint)mWord));
    // ASCII edit
    QString asciiExp;
    for (int i = 0; i < asciiWidth; i++) {
      // replace non-printable chars with a dot
      if (hex[i] != NULL && QChar(hex[i]).isPrint())
        asciiExp.append(QChar(hex[i]));
      else
        asciiExp.append(".");  // dots padding
    }
    ui->asciiLineEdit->setText(asciiExp);

    // Use the same indices, but with different text
    restoreCursorPositions();
  } else {
    ui->expressionLineEdit->setStyleSheet("border: 1px solid red");
    ui->btnOk->setEnabled(false);
  }
}

void WordEditDialog::on_signedLineEdit_textEdited(const QString& arg1) {
  LONGLONG value;
  if (sscanf_s(arg1.toUtf8().constData(), "%lld", &value) == 1) {
    ui->signedLineEdit->setStyleSheet("");
    ui->btnOk->setEnabled(true);
    ui->expressionLineEdit->setText(ToPtrString((duint)value));
  } else {
    ui->signedLineEdit->setStyleSheet("border: 1px solid red");
    ui->btnOk->setEnabled(false);
  }
}

void WordEditDialog::on_unsignedLineEdit_textEdited(const QString& arg1) {
  LONGLONG value;
  if (sscanf_s(arg1.toUtf8().constData(), "%llu", &value) == 1) {
    ui->unsignedLineEdit->setStyleSheet("");
    ui->btnOk->setEnabled(true);
    ui->expressionLineEdit->setText(ToPtrString((duint)value));
  } else {
    ui->unsignedLineEdit->setStyleSheet("border: 1px solid red");
    ui->btnOk->setEnabled(false);
  }
}

void WordEditDialog::saveCursorPositions() {
  // Save the user's cursor index
  mHexLineEditPos = ui->hexLineEdit->cursorPosition();
  mSignedEditPos = ui->signedLineEdit->cursorPosition();
  mUnsignedEditPos = ui->unsignedLineEdit->cursorPosition();
  mAsciiLineEditPos = ui->asciiLineEdit->cursorPosition();
}

void WordEditDialog::restoreCursorPositions() {
  // Load the old cursor index (bounds checking is automatic)
  ui->hexLineEdit->setCursorPosition(mHexLineEditPos);
  ui->signedLineEdit->setCursorPosition(mSignedEditPos);
  ui->unsignedLineEdit->setCursorPosition(mUnsignedEditPos);
  ui->asciiLineEdit->setCursorPosition(mAsciiLineEditPos);
}
